﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using DbMetadata.Configuration;
using DbMetadata.Providers.SqlServer;

namespace DbMetadata.Providers
{
    /// <summary>
    /// Represents a set of static methods to register and create <see cref="IDbMetadataProviderFactory"/> instances.
    /// </summary>
    public static class DbMetadataProviderFactories
    {
        private static readonly ConcurrentDictionary<string, IDbMetadataProviderFactory> _dbMetadataProviderFactories
            = new ConcurrentDictionary<string, IDbMetadataProviderFactory>();

        static DbMetadataProviderFactories()
        {
            RegisterBuiltinProviderFactories();
            RegisterConfiguredProviderFactories();
        }

        /// <summary>
        /// Registers a new <see cref="IDbMetadataProviderFactory"/> instance.
        /// </summary>        
        /// <param name="dbMetadataProviderFactory">The <see cref="IDbMetadataProviderFactory"/> that will be used to create <see cref="DbMetadataProvider"/> instances.</param>
        public static void RegisterMetadataProviderFactory(IDbMetadataProviderFactory dbMetadataProviderFactory)
        {
            _dbMetadataProviderFactories.AddOrUpdate(dbMetadataProviderFactory.ConnectionTypeName, 
                dbMetadataProviderFactory, (key, oldValue) => dbMetadataProviderFactory);
        }


        /// <summary>
        /// Returns the <see cref="IDbMetadataProviderFactory"/> for the requested connection type name.
        /// </summary>
        /// <param name="connectionTypeName">The full type name of the target connection type.</param>
        /// <returns><see cref="IDbMetadataProviderFactory"/></returns>
        public static IDbMetadataProviderFactory GetMetadataProviderFactory(string connectionTypeName)
        {
            IDbMetadataProviderFactory factory; 
            _dbMetadataProviderFactories.TryGetValue(connectionTypeName, out factory);
            if (factory == null)
                throw new InvalidOperationException(string.Format("There is no metadata provider registered for the supplied connection type name ({0})", connectionTypeName));
            return factory;
        }

        /// <summary>
        /// Returns a list of registered <see cref="IDbMetadataProviderFactory"/> instances.
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IDbMetadataProviderFactory> GetFactories()
        {
            return _dbMetadataProviderFactories.Values.ToList();
        }
        


        private static void RegisterBuiltinProviderFactories()
        {
            RegisterMetadataProviderFactory(new SqlMetadataProviderFactory());
        }

        private static void RegisterConfiguredProviderFactories()
        {
            var settings = ConfigurationManager.GetSection("dbMetadata") as DbMetadataSettings;
            if (settings == null)
                return;
            foreach (var providerFactoryElement in settings.ProviderFactories)
            {
                var factoryType = ((ProviderFactoryElement) providerFactoryElement).Type;
                var factory = (IDbMetadataProviderFactory)Activator.CreateInstance(factoryType);
                RegisterMetadataProviderFactory(factory);
            }
        }
    }
}
